/*******************************************************************************
 * Copyright ©  2011 Hulles Industries LLC <hulles.com>
 * All rights reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *     
 *******************************************************************************/
package com.hulles.sancho.engine.entity;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.logging.Level;

import com.hulles.sancho.engine.DbManager;
import com.hulles.sancho.engine.Utils;

/**
 * This is the abstract superclass for all database entities in the Sancho engine.
 * @author hulles
 */
public abstract class SanchoEntity implements Comparable<SanchoEntity> {
    private static final int DISPLAYNAMELENGTH = 20;
    private final transient EntityMonitor monitor;
    private final transient EntityType eType;
    private Connection conn = null;
    private static DbManager dbMgrInstance;

    /**
     * Create a new instance of SanchoEntity
     */
    public SanchoEntity() {
        eType = EntityType.getInstance();
        monitor = EntityMonitor.getInstance();
    }


    protected static synchronized DbManager getDbMgr() {
        if (dbMgrInstance == null) {
            dbMgrInstance = DbManager.getInstance();
        }
        return dbMgrInstance;
    }

    /**
     * Get the entity's primary key.
     * @return The entity's primary database key.
     */
    public abstract Integer getPrimaryKey();

    /**
     * Get the entity's display name.
     * @return The display name.
     */
    public abstract String getDisplayName();

    /**
     * Get a new database ID for the entity. This is a wrapper for the DbManager function of
     * the same name.
     * @param key
     * @return
     */
    protected Integer getNewID() {
        return getDbMgr().getNewID(getEntityType());
    }

    /**
     * Transform a string into a "display name". Typically this is truncated to an
     * arbitrary length.
     * @param text The text to transform
     * @return The display name
     */
    protected static String textToDisplayName(String text) {
        return textToDisplayName(text, DISPLAYNAMELENGTH);
    }

    /**
     * Transform a string into a "display name". The string is truncated to
     * the specified length if necessary.
     * @param text The text to transform
     * @param len The maximum length of the text
     * @return The display name
     */
    protected static String textToDisplayName(String text, int len) {
        return Utils.textToDisplay(text, len);
    }

    /**
     * Set the database connection for an entity. Used primarily for transactions.
     * The connection should be closed by the application after use.
     * @param conn The connection for the entity.
     */
    public synchronized void setConnection(Connection conn) {
        this.conn = conn;
    }

    /**
     * Create a new Sancho Entity in the database.
     * @param sql The SQL statement to execute.
     * @param parent The parent of the new entity.
     */
    protected void newEntitySQL(String sql) {
        if (conn != null) {
            getDbMgr().executeUpdate(sql, conn);
        } else {
            getDbMgr().executeUpdate(sql);
        }
        fireNewEntity(this);
//        Utils.log(Level.FINER, "Creating " + this.getClass().getName());
    }

    /**
     * Update a Sancho Entity in the database.
     * @param sql The SQL statement to execute.
     */
    protected void updateEntitySQL(String sql) {
        if (conn != null) {
            getDbMgr().executeUpdate(sql, conn);
        } else {
            getDbMgr().executeUpdate(sql);
        }
        fireUpdateEntity(this);
//        Utils.log(Level.FINER, "Updating" + this.getClass().getName());
    }

    /**
     * Delete a Sancho Entity in the database.
     * @param sql The SQL statement to execute.
     */
    protected void deleteEntitySQL(String sql) {
        if (conn != null) {
            getDbMgr().executeUpdate(sql, conn);
        } else {
            getDbMgr().executeUpdate(sql);
        }
        fireDeleteEntity(this);
//        Utils.log(Level.FINER, "Deleting " + this.getClass().getName());
    }

    /**
     * Get the entity type key for the entity.
     * @return The entity type key
     */
    abstract public String getEntityType();

    /**
     * Delete the entity.
     */
    abstract public void delete();

    @Override
    abstract public boolean equals(Object obj);

    @Override
    abstract public int hashCode();

    @Override
    public String toString() {
        return getDisplayName();
    }

    @Override
    public int compareTo(SanchoEntity o) {
        int entityCmp;
        String myName;
        String theirName;

        myName = this.getDisplayName();
        theirName = o.getDisplayName();
        entityCmp = myName.compareTo(theirName);
        return entityCmp;
    }

    /***** LISTENER EVENTS ****/

    /**
     * Fire the "update" event for entity monitor listeners
     * @param entity The entity being updated
     */
    protected void fireUpdateEntity(SanchoEntity entity) {
        String type;

        type = entity.getEntityType();
        Utils.log(Level.FINER, "SanchoEntity: firing entity changed for " + eType.getDisplayName(type));
//        if (!isTransactionEnabled()) {
            monitor.fireUpdateEntity(entity);
//        }
    }

    /**
     * Fire the "new" event for entity monitor listeners
     * @param entity The new entity
     * @param parent The entity's arbitrary "parent"
     */
    protected void fireNewEntity(SanchoEntity entity) {
        String type;

        type = entity.getEntityType();
        Utils.log(Level.FINER, "SanchoEntity: firing new entity for entity " + eType.getDisplayName(type));
//        if (!isTransactionEnabled()) {
            monitor.fireNewEntity(entity);
//        }
    }

    /**
     * Fire the "delete" event for entity monitor listeners
     * @param entity The entity being deleted
     */
    protected void fireDeleteEntity(SanchoEntity entity) {
        Utils.log(Level.FINER, "SanchoEntity: firing delete entity");
//        if (!isTransactionEnabled()) {
            monitor.fireDeleteEntity(entity);
//        }
    }

    /**
     * Create a new instance of a SanchoEntity (e.g SanchoPerson) from key and entity type
     * @param entityID The Integer key for the instance
     * @param className The name of the SanchoEntity class
     * @return The instantiated entity or null
     */
    @SuppressWarnings("unchecked")
    public static <T extends SanchoEntity> SanchoEntity createSanchoEntity(Integer entityID, String className) {
        SanchoEntity entity;
        Class<SanchoEntity> eClass = null;
        Constructor<SanchoEntity> constructor = null;

        if (entityID == 0 || entityID == null || className == null) {
            Utils.error("Missing argument in createSanchoEntity");
            return null;
        }

        try {
            eClass = (Class<SanchoEntity>)Class.forName(className);
        } catch (ClassNotFoundException ex) {
            Utils.error("Unable to find class for entity class " + className, ex);
            return null;
        }

        if (eClass == null) {
            Utils.error("Got no class! (unable to create class)");
            return null;
        }

        try {
            constructor = eClass.getConstructor(Integer.class);
        } catch (NoSuchMethodException ex) {
            Utils.error("SanchoEntity: No such method", ex);
            return null;
        } catch (SecurityException ex) {
            Utils.error("SanchoEntity: Security exception", ex);
            return null;
        }

        try {
            entity = constructor.newInstance(entityID);
        } catch (InstantiationException ex) {
            Utils.error("SanchoEntity: Instantiation exception", ex);
            return null;
        } catch (IllegalAccessException ex) {
            Utils.error("SanchoEntity: Illegal access exception", ex);
            return null;
        } catch (IllegalArgumentException ex) {
            Utils.error("SanchoEntity: Illegal argument exception", ex);
            return null;
        } catch (InvocationTargetException ex) {
            Utils.error("SanchoEntity: Invocation target exception", ex);
            return null;
        }
        return entity;
    }
}
